﻿using System;
using System.Runtime.InteropServices;

namespace MidiPianoTray
{
    class Midi
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct HMidiIn
        {
            public IntPtr handle;
        };

        [StructLayout(LayoutKind.Sequential)]
        public struct HMidiOut
        {
            public IntPtr handle;
        };

        public delegate void MidiInProc(HMidiIn hmi, UInt32 wMsg, UIntPtr dwInstance, UIntPtr dwParam1, UIntPtr dwParam2);
        public delegate void MidiOutProc(HMidiOut hmo, UInt32 wMsg, UIntPtr dwInstance, UIntPtr dwParam1, UIntPtr dwParam2);

        private const uint CALLBACK_FUNCTION = 0x00030000;

        internal static class NativeMethods
        {
            [DllImport("winmm.dll", SetLastError = true)]
            internal static extern int midiInGetNumDevs();

            [DllImport("winmm.dll")]
            internal static extern int midiOutGetNumDevs();

            [StructLayout(LayoutKind.Sequential)]
            internal struct MIDIOUTCAPS
            {
                public ushort    wMid;
                public ushort    wPid;
                public uint      vDriverVersion;
                [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
                public string   szPname;
                public ushort    wTechnology;
                public ushort    wVoices;
                public ushort    wNotes;
                public ushort    wChannelMask;
                public uint   dwSupport;
            }

            [StructLayout(LayoutKind.Sequential)]
            internal struct MIDIINCAPS
            {
                public ushort wMid;
                public ushort wPid;
                public uint vDriverVersion;
                [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
                public string szPname;
                public uint dwSupport;
            }

            [DllImport("winmm.dll", SetLastError = true)]
            internal static extern uint midiOutGetDevCaps(UIntPtr uDeviceID, out MIDIOUTCAPS caps, uint cbMidiOutCaps);

            [DllImport("winmm.dll", SetLastError = true)]
            internal static extern uint midiInGetDevCaps(UIntPtr uDeviceID, out MIDIINCAPS caps, uint cbMidiInCaps);

            [DllImport("winmm.dll", SetLastError = true)]
            internal static extern uint midiInOpen(out IntPtr hmi, UInt32 uDeviceID,
                MidiInProc dwCallback, UIntPtr dwCallbackInstance, UInt32 dwFlags);

            [DllImport("winmm.dll", SetLastError = true)]
            internal static extern uint midiInStart(IntPtr hmi);

            [DllImport("winmm.dll", SetLastError = true)]
            internal static extern uint midiInStop(IntPtr hmi);

            [DllImport("winmm.dll", SetLastError = true)]
            internal static extern uint midiInReset(IntPtr hmi);

            [DllImport("winmm.dll", SetLastError = true)]
            internal static extern uint midiInClose(IntPtr hmi);

            [DllImport("winmm.dll", SetLastError = true)]
            internal static extern uint midiOutOpen(out IntPtr hmo, UInt32 uDeviceID,
                MidiOutProc dwCallback, UIntPtr dwCallbackInstance, UInt32 dwFlags);

            [DllImport("winmm.dll", SetLastError = true)]
            internal static extern uint midiOutClose(IntPtr hmo);

            [DllImport("winmm.dll", SetLastError = true)]
            internal static extern uint midiOutShortMsg(IntPtr hmo, UInt32 dwMsg);

            [DllImport("winmm.dll", SetLastError = true)]
            internal static extern uint midiOutReset(IntPtr hmo);
        }
        
        ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public static int InGetNumDevs() {
            return NativeMethods.midiInGetNumDevs();
        }

        public static int OutGetNumDevs() {
            return NativeMethods.midiOutGetNumDevs();
        }

        public class MidiInCaps
        {
            public string name;
            public int mid;
            public int pid;
        };

        public class MidiOutCaps
        {
            public string name;
            public int mid;
            public int pid;
        };

        public static MidiInCaps InGetDevCaps(uint deviceId) {
            MidiInCaps result = null;

            UIntPtr uDevId = new UIntPtr(deviceId);
            var caps = new NativeMethods.MIDIINCAPS();

            var rv = NativeMethods.midiInGetDevCaps(uDevId, out caps, (uint)Marshal.SizeOf(typeof(NativeMethods.MIDIINCAPS)));
            if (0 == rv) {
                result = new MidiInCaps();
                result.name = caps.szPname;
                result.mid = caps.wMid;
                result.pid = caps.wPid;
            }

            return result;
        }

        public static MidiOutCaps OutGetDevCaps(uint deviceId) {
            MidiOutCaps result = null;

            UIntPtr uDevId = new UIntPtr(deviceId);
            var caps = new NativeMethods.MIDIOUTCAPS();

            var rv = NativeMethods.midiOutGetDevCaps(uDevId, out caps, (uint)Marshal.SizeOf(typeof(NativeMethods.MIDIOUTCAPS)));
            if (0 == rv) {
                result = new MidiOutCaps();
                result.name = caps.szPname;
                result.mid = caps.wMid;
                result.pid = caps.wPid;
            }

            return result;
        }

        public static uint InOpen(out HMidiIn hmi, uint deviceId, MidiInProc dwCallback) {
            UIntPtr dwCallbackInstance = new UIntPtr(0);
            return NativeMethods.midiInOpen(out hmi.handle, deviceId, dwCallback, dwCallbackInstance, CALLBACK_FUNCTION);
        }

        public static uint InStart(HMidiIn hMidiIn) {
            return NativeMethods.midiInStart(hMidiIn.handle);
        }

        public static uint InStop(HMidiIn hMidiIn) {
            return NativeMethods.midiInStop(hMidiIn.handle);
        }

        public static uint InClose(HMidiIn hMidiIn) {
            return NativeMethods.midiInClose(hMidiIn.handle);
        }

        public static uint InReset(HMidiIn hMidiIn) {
            return NativeMethods.midiInReset(hMidiIn.handle);
        }

        public static uint OutOpen(out HMidiOut hmo, uint deviceId, MidiOutProc dwCallback) {
            UIntPtr dwCallbackInstance = new UIntPtr(0);
            return NativeMethods.midiOutOpen(out hmo.handle, deviceId, dwCallback, dwCallbackInstance, CALLBACK_FUNCTION);
        }

        public static uint OutShortMsg(HMidiOut hmo, uint msg) {
            return NativeMethods.midiOutShortMsg(hmo.handle, msg);
        }

        public static uint OutClose(HMidiOut hmo) {
            return NativeMethods.midiOutClose(hmo.handle);
        }

        public static uint OutReset(HMidiOut hmo) {
            return NativeMethods.midiOutReset(hmo.handle);
        }

        public enum Command : uint {
            NoteOff = 0x80,
            NoteOn = 0x90,
            ProgramChange = 0xc0
        }

        public enum ProgramType : uint {
            AcousticGrandPiano,
            BrightAcousticPiano,
            ElectricGrandPiano,
            HonkyTonkPiano,
            ElectricPiano1,
            ElectricPiano2,
            Harpsichord,
            Clavinet,

            Celesta,
            Glockenspiel,
            MusicBox,
            Vibraphone,
            Marimba,
            Xylophone,
            TubularBells,
            Dulcimer,

            DrawbarOrgan,
            PercussiveOrgan,
            RockOrgan,
            ChurchOrgan,
            ReedOrgan,
            Accordion,
            Harmonica,
            TangoAccordion,

            AcousticGuitarNylon,
            AcousticGuitarSteel,
            ElectricGuitarJazz,
            ElectricGuitarClean,
            ElectricGuitarMuted,
            OverdrivenGuitar,
            DistortionGuitar,
            GuitarHarmonics,

            AcousticBass,
            ElectricBassFinger,
            ElectricBassPick,
            FretlessBass,
            SlapBass1,
            SlapBass2,
            SynthBass1,
            SynthBass2,

            Violin,
            Viola,
            Cello,
            Contrabass,
            TremoloStrings,
            PizzicatoStrings,
            OrchestralHarp,
            Timpani,

            StringEnsemble1,
            StringEnsemble2,
            SynthStrings1,
            SynthStrings2,
            ChoirAahs,
            VoiceOohs,
            SynthChoir,
            OrchestraHit,

            Trumpet,
            Trombone,
            Tuba,
            MutedTrumpet,
            FrenchHorn,
            BrassSection,
            SynthBrass1,
            SynthBrass2,

            SopranoSax,
            AltoSax,
            TenorSax,
            BaritoneSax,
            Oboe,
            EnglishHorn,
            Bassoon,
            Clarinet,

            Piccolo,
            Flute,
            Recorder,
            PanFlute,
            BlownBottle,
            Shakuhachi,
            Whistle,
            Ocarina,

            Lead1Square,
            Lead2Sawtooth,
            Lead3Calliope,
            Lead4Chiff,
            Lead5Charang,
            Lead6Voice,
            Lead7Fifths,
            Lead8BassPlusLead,

            Pad1Newage,
            Pad2Warm,
            Pad3Polysynth,
            Pad4Choir,
            Pad5Bowed,
            Pad6Metallic,
            Pad7Halo,
            Pad8Sweep,

            FX1Rain,
            FX2Soundtrack,
            FX3Crystal,
            FX4Atmosphere,
            FX5Brightness,
            FX6Goblins,
            FX7Echoes,
            FX8SciFi,

            Sitar,
            Banjo,
            Shamisen,
            Koto,
            Kalimba,
            Bagpipe,
            Fiddle,
            Shanai,

            TinkleBell,
            Agogo,
            SteelDrums,
            Woodblock,
            TaikoDrum,
            MelodicTom,
            SynthDrum,
            ReverseCymbal,

            GuitarFretNoise,
            BreathNoise,
            Seashore,
            BirdTweet,
            TelephoneRing,
            Helicopter,
            Applause,
            Gunshot,
        }
    }
}
